--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 8d059acc181b0b232de2299d0c374ca7c70611fb
Parents : 396c8af
Author : Mark Qvist <bc7291552be7a58f361522990465165c>
Signature : T66BB85Valid, signed by author
Date : 2026-07-20T01:27:43+02:00
Reworked map caching and layer handling. Added individual map layer toggles to settings. Added ability to use custom MT api key.
Changes
5 files changed, 337 insertions(+), 128 deletions(-)
Diff
diff --git a/sbapp/mapview/downloader.py b/sbapp/mapview/downloader.py
index e5c7ec78..16788907 100644
--- a/sbapp/mapview/downloader.py
+++ b/sbapp/mapview/downloader.py
@@ -4,7 +4,7 @@ __all__ = ["Downloader"]
import logging
import traceback
-from concurrent.futures import ThreadPoolExecutor, TimeoutError, as_completed
+from concurrent.futures import ThreadPoolExecutor, TimeoutError, CancelledError, as_completed
import os
import tempfile
from os import environ, makedirs
@@ -137,7 +137,10 @@ class Downloader:
response.raise_for_status()
data = response.content
dir_name = os.path.dirname(cache_fn)
- if not exists(dir_name): makedirs(dir_name)
+ try:
+ if not exists(dir_name): makedirs(dir_name)
+ except Exception as e:
+ if not exists(dir_name): RNS.log(f"Could not create map directory: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
fd, tmp_path = tempfile.mkstemp(dir=dir_name, prefix=".tile_")
try:
with os.fdopen(fd, "wb") as f: f.write(data)
@@ -166,8 +169,12 @@ class Downloader:
response.raise_for_status()
data = response.content
dir_name = os.path.dirname(cache_fn)
- if not exists(dir_name): makedirs(dir_name)
- fd, tmp_path = tempfile.mkstemp(dir=dir_name, prefix=".tile_")
+ try:
+ if not exists(dir_name): makedirs(dir_name)
+ except Exception as e:
+ if not exists(dir_name): RNS.log(f"Could not create map directory: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
+
+ fd, tmp_path = tempfile.mkstemp(dir=dir_name, prefix=f".tile_{time()}")
try:
with os.fdopen(fd, "wb") as f: f.write(data)
os.replace(tmp_path, cache_fn)
@@ -175,7 +182,8 @@ class Downloader:
try: os.unlink(tmp_path)
except: pass
raise
- except Exception as e: RNS.log(f"Error while prefetching map tile: {e}", RNS.LOG_WARNING) if RNS.sl(RNS.LOG_DEBUG) else None
+ except Exception as e: RNS.log(f"Error while prefetching map tile: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
+ # finally: RNS.log(f"Prefetched tile in {RNS.prettyshorttime(time()-st)}: {uri}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
return None
def _check_executor(self, dt):
@@ -184,9 +192,10 @@ class Downloader:
for future in as_completed(self._futures[:], 0):
self._futures.remove(future)
try: result = future.result()
+ except CancelledError: continue
except Exception as e:
- RNS.log(f"Error while getting tile downloader futures result: {e}", RNS.LOG_WARNING) if RNS.sl(RNS.LOG_DEBUG) else None
- # RNS.trace_exception(e) if RNS.sl(RNS.LOG_DEBUG) else None
+ RNS.log(f"Error while getting tile downloader futures result: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
+ RNS.trace_exception(e) if RNS.sl(RNS.LOG_DEBUG) else None
continue
if result is None: continue
diff --git a/sbapp/mapview/mbtsource.py b/sbapp/mapview/mbtsource.py
index afb770df..299a9bb9 100644
--- a/sbapp/mapview/mbtsource.py
+++ b/sbapp/mapview/mbtsource.py
@@ -1,5 +1,6 @@
__all__ = ["MBTilesMapSource"]
+import RNS
import io
import sqlite3
import threading
@@ -19,11 +20,11 @@ class MBTilesMapSource(MapSource):
# Read metadata
c = self.db.cursor()
metadata = dict(c.execute("SELECT * FROM metadata"))
- if metadata["format"] == "pbf": raise ValueError("Only raster maps are supported, not vector maps.")
+ if metadata["format"] == "pbf": raise ValueError("Only raster maps are supported, not vector maps")
+ RNS.log(f"Loaded local MBTiles map:\n{metadata}", RNS.LOG_DEBUG)
self.min_zoom = int(metadata["minzoom"])
self.max_zoom = int(metadata["maxzoom"])
- self.attribution = metadata.get("attribution", "")
- self.bounds = bounds = None
+ self.attribution = "Local MBTiles" # metadata.get("attribution", "")
cx = cy = 0.0
cz = 5
if "bounds" in metadata: self.bounds = bounds = tuple(map(float, metadata["bounds"].split(",")))
diff --git a/sbapp/mapview/source.py b/sbapp/mapview/source.py
index 0f29b560..9790f10b 100644
--- a/sbapp/mapview/source.py
+++ b/sbapp/mapview/source.py
@@ -12,29 +12,31 @@ from mapview.utils import clamp
class MapSource:
- mt_key = "TUX5omZx8Sqgh9JDquwf"
+ mt_key = ""
attribution_osm = '© OpenStreetMap contributors'
attribution_ve = '© VirtualEarth'
attribution_mt = '© MapTiler © OpenStreetMap contributors'
+ providers = {}
- # list of available providers
- # cache_key: (is_overlay, minzoom, maxzoom, url, attribution)
- providers = {
- "testing": (0, 0, 19, "https://api.maptiler.com/maps/topo-v4/256/{z}/{x}/{y}.png?key="+mt_key, attribution_mt, 256),
- "osm": (0, 0, 19, "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", attribution_osm, 256),
- "mt_outdoor": (0, 0, 19, "https://api.maptiler.com/maps/outdoor-v4/256/{z}/{x}/{y}.png?key="+mt_key, attribution_mt, 256),
- "mt_topo": (0, 0, 19, "https://api.maptiler.com/maps/topo-v4/256/{z}/{x}/{y}.png?key="+mt_key, attribution_mt, 256),
- "mt_hybrid": (0, 0, 19, "https://api.maptiler.com/maps/hybrid-v4/256/{z}/{x}/{y}.jpg?key="+mt_key, attribution_mt, 256),
- "virtualearth": (0, 0, 19, "http://ecn.t3.tiles.virtualearth.net/tiles/a{q}.jpeg?g=1", attribution_ve, 256),
- "opentopo": (0, 0, 17, "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", attribution_osm, 256),
- }
-
- def __init__(self, url="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", cache_key=None,
+ @staticmethod
+ def update_providers():
+ MapSource.providers = {
+ "osm": ("OpenStreetMap", 0, 0, 19, "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", MapSource.attribution_osm, 256),
+ "mt_topo": ("Topographic", 0, 0, 19, "https://api.maptiler.com/maps/topo-v4/256/{z}/{x}/{y}.png?key="+MapSource.mt_key, MapSource.attribution_mt, 256),
+ "mt_outdoor": ("Outdoor", 0, 0, 19, "https://api.maptiler.com/maps/outdoor-v4/256/{z}/{x}/{y}.png?key="+MapSource.mt_key, MapSource.attribution_mt, 256),
+ "mt_hybrid": ("Hybrid", 0, 0, 19, "https://api.maptiler.com/maps/hybrid-v4/256/{z}/{x}/{y}.jpg?key="+MapSource.mt_key, MapSource.attribution_mt, 256),
+ "virtualearth": ("VirtualEarth", 0, 0, 19, "http://ecn.t3.tiles.virtualearth.net/tiles/a{q}.jpeg?g=1", MapSource.attribution_ve, 256),
+ "opentopo": ("OpenTopo", 0, 0, 17, "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", MapSource.attribution_osm, 256),
+ "testing": ("Testing", 0, 0, 19, "https://api.maptiler.com/maps/topo-v4/256/{z}/{x}/{y}.png?key="+MapSource.mt_key, MapSource.attribution_mt, 256),
+ }
+
+ def __init__(self, name="Map", url="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", cache_key=None,
min_zoom=0, max_zoom=19, tile_size=256, image_ext="png", attribution="© OpenStreetMap contributors",
subdomains="abc", quad_key = False, **kwargs):
# Possible cache hit, but very unlikely
if cache_key is None: cache_key = hashlib.sha224(url.encode("utf8")).hexdigest()[:10]
+ self.name = name
self.url = url
self.cache_key = cache_key
self.min_zoom = min_zoom
@@ -44,11 +46,11 @@ class MapSource:
self.attribution = attribution
self.subdomains = subdomains
self.quad_key = quad_key
- self.cache_fmt = "{cache_key}_{zoom}_{tile_x}_{tile_y}.{image_ext}"
+ self.cache_fmt = "{zoom}/{tile_x}/{tile_y}.{image_ext}"
self.dp_tile_size = min(dp(self.tile_size), self.tile_size * 2)
self.default_lat = self.default_lon = self.default_zoom = None
self.bounds = None
- self.cache_dir = kwargs.get('cache_dir', CACHE_DIR)
+ self.cache_dir = kwargs.get('cache_dir', CACHE_DIR)+str(f"/{self.name}")
@staticmethod
def from_provider(key, **kwargs):
@@ -56,12 +58,9 @@ class MapSource:
provider = MapSource.providers[key]
cache_dir = kwargs.get('cache_dir', CACHE_DIR)
options = {}
- is_overlay, min_zoom, max_zoom, url, attribution, tile_size = provider[:6]
- if len(provider) > 6: options = provider[6:]
- import RNS # TODO: Remove
- RNS.log(f"PROVIDER: {provider}")
- RNS.log(f"OPTIONS: {options}")
- return MapSource(cache_key=key, min_zoom=min_zoom, max_zoom=max_zoom,
+ name, is_overlay, min_zoom, max_zoom, url, attribution, tile_size = provider[:7]
+ if len(provider) > 7: options = provider[7:]
+ return MapSource(name=name, cache_key=key, min_zoom=min_zoom, max_zoom=max_zoom,
url=url, cache_dir=cache_dir, attribution=attribution,
quad_key=quad_key, tile_size=tile_size, **options)
diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py
index 4cd0e5f3..45bfbcb5 100644
--- a/sbapp/sideband/core.py
+++ b/sbapp/sideband/core.py
@@ -603,18 +603,33 @@ class SidebandCore():
self.__save_config(no_thread=True)
- def clear_map_cache(self):
- for entry in os.scandir(self.map_cache):
- os.unlink(entry.path)
-
- def get_map_cache_size(self):
- total = 0
+ def clear_map_cache(self, name):
+ if not name: return
+ target = f"{self.map_cache}/{name}"
+ if not os.path.isdir(target): return
+
+ # Clear any old map cache files from
+ # previous cache structure
for entry in os.scandir(self.map_cache):
- if entry.is_dir(follow_symlinks=False):
- pass
- else:
- total += entry.stat(follow_symlinks=False).st_size
- return total
+ try:
+ if os.path.isfile(entry.path): os.unlink(entry.path)
+ except: pass
+
+ # Remove cached data for selected map
+ import shutil
+ if os.path.isdir(target): shutil.rmtree(target, ignore_errors=True)
+
+ def get_map_cache_size(self, name):
+ if not name: return 0
+ try:
+ from pathlib import Path
+ cache_directory = Path(f"{self.map_cache}/{name}")
+ total = sum(f.stat().st_size for f in cache_directory.glob('**/*') if f.is_file())
+ return total
+ except Exception as e:
+ RNS.log(f"Could not calculate map cache size for {active_map}: {e}", RNS.LOG_ERROR)
+ RNS.trace_exception(e)
+ return 0
def should_persist_data(self, background=False):
if self.reticulum != None: self.reticulum._should_persist_data(background=background)
@@ -803,11 +818,20 @@ class SidebandCore():
if not "map_storage_external" in self.config: self.config["map_storage_external"] = False
if not "map_use_offline" in self.config: self.config["map_use_offline"] = False
if not "map_use_online" in self.config: self.config["map_use_online"] = True
+ if not "map_mt_api_key" in self.config: self.config["map_mt_api_key"] = ""
if not "map_layer" in self.config: self.config["map_layer"] = None
if not "map_cluster" in self.config: self.config["map_cluster"] = True
if not "map_interfaces" in self.config: self.config["map_interfaces"] = True
if not "map_connection_maps" in self.config: self.config["map_connection_maps"] = False
if not "map_relaxed_loading" in self.config: self.config["map_relaxed_loading"] = False
+ if not "map_layer" in self.config: self.config["map_layer"] = "osm"
+ if not "map_use_osm" in self.config: self.config["map_use_osm"] = True
+ if not "map_use_topo" in self.config: self.config["map_use_topo"] = True
+ if not "map_use_outdoor" in self.config: self.config["map_use_outdoor"] = True
+ if not "map_use_hybrid" in self.config: self.config["map_use_hybrid"] = True
+ if not "map_use_virtualearth" in self.config: self.config["map_use_virtualearth"] = True
+ if not "map_use_opentopo" in self.config: self.config["map_use_opentopo"] = True
+ if not "map_use_custom_1" in self.config: self.config["map_use_custom_1"] = False
if not "discover_interfaces" in self.config: self.config["discover_interfaces"] = True
if not "map_storage_path" in self.config: self.config["map_storage_path"] = None
diff --git a/sbapp/ui/map.py b/sbapp/ui/map.py
index 9adb5267..1ba3d36f 100644
--- a/sbapp/ui/map.py
+++ b/sbapp/ui/map.py
@@ -38,11 +38,14 @@ else:
class Map():
IF_UPDATE_INTERVAL = 60
+ DEFAULT_LAYER = "osm"
+ QUAD_KEY_LAYERS = ["virtualearth"]
+ DEFAULT_MT_API = "GDVyAENrfG4zrzxNUXjS"
+
def __init__(self, app):
self.app = app
self.screen = None
self.map_markers = {}
- self.map_layer = None
self.map_cache = self.app.sideband.map_cache
self.offline_source = None
self.clustered_layer = None
@@ -50,8 +53,11 @@ class Map():
self.interface_discovery_hashes = {}
self.connection_map_hashes = {}
self.last_interfaces_update = 0
+ self.map_cache_size = 0
if not self.app.root.ids.screen_manager.has_screen("map_screen"):
+ MapSource.mt_key = self.DEFAULT_MT_API if not self.app.sideband.config["map_mt_api_key"] else self.app.sideband.config["map_mt_api_key"]
+ MapSource.update_providers()
msource = self.get_source()
mzoom = self.app.sideband.config["map_zoom"]
mlat = self.app.sideband.config["map_lat"]; mlon = self.app.sideband.config["map_lon"]
@@ -129,7 +135,8 @@ class Map():
map_dialog.open()
self.app.sideband.config["map_storage_file"] = None
self.app.sideband.config["map_use_offline"] = False
- self.app.sideband.config["map_use_online"] = True
+ self.app.sideband.config["map_use_osm"] = True
+ self.app.sideband.config["map_layer"] = "osm"
self.settings_load_states()
self.update_source()
@@ -195,8 +202,7 @@ class Map():
else:
try:
current_map_path = self.app.sideband.config["map_storage_file"]
- if current_map_path == None:
- raise ValueError("Map path cannot be None")
+ if current_map_path == None: raise ValueError("Map path cannot be None")
source = MBTilesMapSource(current_map_path, cache_dir=self.map_cache)
self.offline_source = source
return self.offline_source
@@ -205,7 +211,8 @@ class Map():
RNS.log(f"Error while loading map from \"{current_map_path}\": "+str(e))
self.app.sideband.config["map_storage_file"] = None
self.app.sideband.config["map_use_offline"] = False
- self.app.sideband.config["map_use_online"] = True
+ self.app.sideband.config["map_use_osm"] = True
+ self.app.sideband.config["map_layer"] = "osm"
self.app.sideband.save_configuration()
self.settings_load_states()
@@ -213,8 +220,18 @@ class Map():
def get_source(self):
source = None
- if self.app.sideband.config["map_use_offline"]: source = self.get_offline_source()
- if source == None: source = MapSource.from_provider("testing", cache_dir=self.map_cache, quad_key=False)
+ layers = self.get_layers()
+ selected_layer = self.app.sideband.config["map_layer"]
+ if not selected_layer in layers:
+ selected_layer = "osm"
+ self.app.sideband.config["map_layer"] = selected_layer
+ self.app.sideband.config["map_use_osm"] = True
+
+ if selected_layer == "offline": source = self.get_offline_source()
+ if source == None:
+ if not selected_layer in MapSource.providers: selected_layer = self.DEFAULT_LAYER
+ use_quad_key = True if selected_layer in self.QUAD_KEY_LAYERS else False
+ source = MapSource.from_provider(selected_layer, cache_dir=self.map_cache, quad_key=use_quad_key)
return source
def update_source(self, source=None):
@@ -225,10 +242,12 @@ class Map():
maxz = source.max_zoom
minz = source.min_zoom
if self.map.zoom > maxz:
- mz = maxz; px, py = self.get_zoom_center(); self.map.set_zoom_at(mz, px, py)
+ mz = maxz; px, py = self.get_zoom_center()
+ self.map.set_zoom_at(mz, px, py)
if self.map.zoom < minz:
- mz = minz; px, py = self.get_zoom_center(); self.map.set_zoom_at(mz, px, py)
+ mz = minz; px, py = self.get_zoom_center()
+ self.map.set_zoom_at(mz, px, py)
m = self.map
nlat = self.map.lat
@@ -251,42 +270,43 @@ class Map():
if self.app.sideband.config["map_connection_maps"]: self.app.sideband.config["map_cluster"] = True
self.update_markers()
- def layers_action(self, sender=None):
+ def get_layers(self):
+ layers = []
+ if self.app.sideband.config["map_use_offline"]: layers.append("offline")
+ if self.app.sideband.config["map_use_custom_1"]: layers.append("custom_1")
+ if self.app.sideband.config["map_use_osm"]: layers.append("osm")
+ if self.app.sideband.config["map_use_topo"]: layers.append("mt_topo")
+ if self.app.sideband.config["map_use_outdoor"]: layers.append("mt_outdoor")
+ if self.app.sideband.config["map_use_hybrid"]: layers.append("mt_hybrid")
+ if self.app.sideband.config["map_use_opentopo"]: layers.append("opentopo")
+ if self.app.sideband.config["map_use_virtualearth"]: layers.append("virtualearth")
+ if len(layers) == 0: layers.append("osm")
+ return layers
+
+ def layers_action(self, sender=None, cycle=True):
try:
- ml = self.map_layer
- layers = []
- if self.app.sideband.config["map_use_offline"]: layers.append("offline")
- if self.app.sideband.config["map_use_online"]:
- layers.append("testing")
- layers.append("osm")
- layers.append("mt_outdoor")
- layers.append("mt_topo")
- layers.append("mt_hybrid")
- layers.append("opentopo")
- layers.append("virtualearth")
-
- if ml == None: ml = layers[0]
+ ml = self.app.sideband.config["map_layer"]
+ layers = self.get_layers()
+
if not ml in layers: ml = layers[0]
mli = layers.index(ml)
- mli = (mli+1)%len(layers)
+ if cycle: mli = (mli+1)%len(layers)
ml = layers[mli]
source = None
- if ml == "offline": source = self.get_offline_source()
- elif ml == "testing": source = MapSource.from_provider("testing", cache_dir=self.map_cache, quad_key=False)
- elif ml == "osm": source = MapSource.from_provider("osm", cache_dir=self.map_cache, quad_key=False)
- elif ml == "mt_outdoor": source = MapSource.from_provider("mt_outdoor", cache_dir=self.map_cache, quad_key=False)
- elif ml == "mt_topo": source = MapSource.from_provider("mt_topo", cache_dir=self.map_cache, quad_key=False)
- elif ml == "mt_hybrid": source = MapSource.from_provider("mt_hybrid", cache_dir=self.map_cache, quad_key=False)
- elif ml == "opentopo": source = MapSource.from_provider("opentopo", cache_dir=self.map_cache, quad_key=False)
- elif ml == "virtualearth": source = MapSource.from_provider("virtualearth", cache_dir=self.map_cache, quad_key=True)
+ use_quad_key = True if ml in self.QUAD_KEY_LAYERS else False
+
+ if ml == "offline": source = self.get_offline_source()
+ else: source = MapSource.from_provider(ml, cache_dir=self.map_cache, quad_key=use_quad_key)
- toast(f"Using map \"{ml}\"")
+ source_name = source.name if ml != "offline" else "Local Map"
+ toast(f"{source_name}")
if source != None:
- self.map_layer = ml
self.update_source(source)
+ self.app.sideband.config["map_layer"] = ml
+ self.app.sideband.save_configuration()
except Exception as e:
RNS.log("Error while switching map layer: "+str(e), RNS.LOG_ERROR)
@@ -352,8 +372,13 @@ class Map():
def settings_load_states(self):
if self.map_settings_screen != None:
- self.map_settings_screen.ids.map_use_online.active = self.app.sideband.config["map_use_online"]
self.map_settings_screen.ids.map_use_offline.active = self.app.sideband.config["map_use_offline"]
+ self.map_settings_screen.ids.map_use_osm.active = self.app.sideband.config["map_use_osm"]
+ self.map_settings_screen.ids.map_use_topo.active = self.app.sideband.config["map_use_topo"]
+ self.map_settings_screen.ids.map_use_outdoor.active = self.app.sideband.config["map_use_outdoor"]
+ self.map_settings_screen.ids.map_use_hybrid.active = self.app.sideband.config["map_use_hybrid"]
+ self.map_settings_screen.ids.map_use_opentopo.active = self.app.sideband.config["map_use_opentopo"]
+ self.map_settings_screen.ids.map_use_virtualearth.active = self.app.sideband.config["map_use_virtualearth"]
self.map_settings_screen.ids.map_storage_external.active = self.app.sideband.config["map_storage_external"]
self.map_settings_screen.ids.map_cluster.active = self.app.sideband.config["map_cluster"]
self.map_settings_screen.ids.map_interfaces.active = self.app.sideband.config["map_interfaces"]
@@ -364,12 +389,20 @@ class Map():
self.settings_load_states()
def map_settings_save(sender=None, event=None):
self.app.sideband.config["map_storage_external"] = self.map_settings_screen.ids.map_storage_external.active
- self.app.sideband.config["map_use_online"] = self.map_settings_screen.ids.map_use_online.active
self.app.sideband.config["map_use_offline"] = self.map_settings_screen.ids.map_use_offline.active
+ self.app.sideband.config["map_use_osm"] = self.map_settings_screen.ids.map_use_osm.active
+ self.app.sideband.config["map_use_topo"] = self.map_settings_screen.ids.map_use_topo.active
+ self.app.sideband.config["map_use_outdoor"] = self.map_settings_screen.ids.map_use_outdoor.active
+ self.app.sideband.config["map_use_hybrid"] = self.map_settings_screen.ids.map_use_hybrid.active
+ self.app.sideband.config["map_use_opentopo"] = self.map_settings_screen.ids.map_use_opentopo.active
+ self.app.sideband.config["map_use_virtualearth"] = self.map_settings_screen.ids.map_use_virtualearth.active
self.app.sideband.config["map_cluster"] = self.map_settings_screen.ids.map_cluster.active
self.app.sideband.config["map_interfaces"] = self.map_settings_screen.ids.map_interfaces.active
self.app.sideband.config["map_connection_maps"] = self.map_settings_screen.ids.map_connection_maps.active
self.app.sideband.config["map_relaxed_loading"] = self.map_settings_screen.ids.map_relaxed_loading.active
+ self.app.sideband.config["map_mt_api_key"] = str(self.map_settings_screen.ids.map_mt_api_key.text).strip()
+ MapSource.mt_key = self.DEFAULT_MT_API if not self.app.sideband.config["map_mt_api_key"] else self.app.sideband.config["map_mt_api_key"]
+ MapSource.update_providers()
self.app.sideband.save_configuration()
def relaxed_loading_toggle(sender=None, event=None):
@@ -386,25 +419,31 @@ class Map():
self.app.sideband.config["map_storage_path"] = None
map_settings_save()
- def offline_toggle(sender=None, event=None):
- if self.map_settings_screen.ids.map_use_offline.active:
- if self.app.sideband.config["map_storage_file"] == None: self.map_select_file_action()
- else: self.map_settings_screen.ids.map_use_online.active = True
+ def layers_toggle(sender=None, event=None):
map_settings_save(); self.update_source()
- def online_toggle(sender=None, event=None):
- if self.map_settings_screen.ids.map_use_online.active: pass
- else: self.map_settings_screen.ids.map_use_offline.active = True
+ def offline_toggle(sender=None, event=None):
+ if self.map_settings_screen.ids.map_use_offline.active:
+ if self.app.sideband.config["map_storage_file"] == None: self.select_file_action()
+ else: self.map_settings_screen.ids.map_use_osm.active = True
map_settings_save(); self.update_source()
self.map_settings_screen.ids.map_use_offline.bind(active=offline_toggle)
- self.map_settings_screen.ids.map_use_online.bind(active=online_toggle)
+ self.map_settings_screen.ids.map_use_osm.bind(active=layers_toggle)
+ self.map_settings_screen.ids.map_use_topo.bind(active=layers_toggle)
+ self.map_settings_screen.ids.map_use_outdoor.bind(active=layers_toggle)
+ self.map_settings_screen.ids.map_use_hybrid.bind(active=layers_toggle)
+ self.map_settings_screen.ids.map_use_opentopo.bind(active=layers_toggle)
+ self.map_settings_screen.ids.map_use_virtualearth.bind(active=layers_toggle)
self.map_settings_screen.ids.map_storage_external.bind(active=external_toggle)
self.map_settings_screen.ids.map_cluster.bind(active=map_settings_save)
self.map_settings_screen.ids.map_interfaces.bind(active=map_settings_save)
self.map_settings_screen.ids.map_connection_maps.bind(active=map_settings_save)
self.map_settings_screen.ids.map_relaxed_loading.bind(active=relaxed_loading_toggle)
+ self.map_settings_screen.ids.map_mt_api_key.text = self.app.sideband.config["map_mt_api_key"]
+ self.map_settings_screen.ids.map_mt_api_key.bind(focus=map_settings_save)
+
def settings_action(self, sender=None, direction="left"):
if not self.app.root.ids.screen_manager.has_screen("map_settings_screen"):
self.map_settings_screen = Builder.load_string(layout_map_settings_screen)
@@ -422,26 +461,35 @@ class Map():
self.app.root.ids.nav_drawer.set_state("closed")
self.app.sideband.setstate("app.displaying", self.app.root.ids.screen_manager.current)
- def update_cache_size(dt):
- size = self.app.sideband.get_map_cache_size()
- size_str = RNS.prettysize(size)
- self.map_settings_screen.ids.map_cache_button.text = f"Clear {size_str} map cache"
- if size > 0.0:
- self.map_settings_screen.ids.map_cache_button.disabled = False
- else:
- self.map_settings_screen.ids.map_cache_button.disabled = True
- self.map_settings_screen.ids.map_cache_button.text = f"No data in map cache"
+ self.map_settings_screen.ids.map_cache_button.disabled = True
+ self.map_settings_screen.ids.map_cache_button.text = f"Calculating cache size..."
- Clock.schedule_once(update_cache_size, 0.35)
+ def job():
+ map_name = MapSource.providers[self.app.sideband.config["map_layer"]][0] if self.app.sideband.config["map_layer"] in MapSource.providers else None
+ self.map_cache_size = self.app.sideband.get_map_cache_size(map_name)
+ Clock.schedule_once(self.display_map_cache_size, 0.1)
+
+ threading.Thread(target=job, daemon=True).start()
+
+ def display_map_cache_size(self, sender=None):
+ if self.map_cache_size > 0:
+ size_str = RNS.prettysize(self.map_cache_size)
+ self.map_settings_screen.ids.map_cache_button.text = f"Clear {size_str} map cache"
+ self.map_settings_screen.ids.map_cache_button.disabled = False
+ else:
+ self.map_settings_screen.ids.map_cache_button.disabled = True
+ self.map_settings_screen.ids.map_cache_button.text = f"No data in map cache"
def clear_cache(self, sender=None):
yes_button = MDRectangleFlatButton(text="Yes",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_reject, text_color=self.app.color_reject)
no_button = MDRectangleFlatButton(text="No",font_size=dp(18))
- dialog = MDDialog(title="Clear map cache?", buttons=[ yes_button, no_button ])
+ map_name = MapSource.providers[self.app.sideband.config["map_layer"]][0] if self.app.sideband.config["map_layer"] in MapSource.providers else self.app.sideband.config["map_layer"]
+ dialog = MDDialog(title="Clear map cache?", text=f"This will clear all cached tiles for the map {map_name}.", buttons=[ yes_button, no_button ])
def dl_yes(s):
dialog.dismiss()
- self.app.sideband.clear_map_cache()
+ map_name = MapSource.providers[self.app.sideband.config["map_layer"]][0] if self.app.sideband.config["map_layer"] in MapSource.providers else None
+ self.app.sideband.clear_map_cache(map_name)
def cb(dt): self.settings_action()
self.map_settings_screen.ids.map_cache_button.disabled = True
Clock.schedule_once(cb, 1.2)
@@ -882,6 +930,35 @@ MDScreen:
text_size: self.width, None
height: self.texture_size[1]
+ MDBoxLayout:
+ orientation: "vertical"
+ size_hint_y: None
+ spacing: dp(24)
+ height: self.minimum_height
+ padding: [0, dp(24), 0, 0]
+
+ MDRectangleFlatIconButton:
+ id: map_select_button
+ icon: "list-box-outline"
+ text: "Select MBTiles Map"
+ padding: [dp(0), dp(14), dp(0), dp(14)]
+ icon_size: dp(24)
+ font_size: dp(16)
+ size_hint: [1.0, None]
+ on_release: root.delegate.select_file_action(self)
+ disabled: False
+
+ MDRectangleFlatIconButton:
+ id: map_cache_button
+ icon: "map-clock-outline"
+ text: "Clear map cache"
+ padding: [dp(0), dp(14), dp(0), dp(14)]
+ icon_size: dp(24)
+ font_size: dp(16)
+ size_hint: [1.0, None]
+ on_release: root.delegate.clear_cache(self)
+ disabled: False
+
MDLabel:
markup: True
text: "\\n"
@@ -889,6 +966,20 @@ MDScreen:
text_size: self.width, None
height: self.texture_size[1]
+ MDLabel:
+ text: "Display Options"
+ font_style: "H5"
+ size_hint_y: None
+ height: self.texture_size[1]
+ padding: [0, 0, 0, dp(12)]
+
+ MDLabel:
+ id: settings_info1
+ markup: True
+ text: ""
+ size_hint_y: None
+ height: self.texture_size[1]
+
MDBoxLayout:
orientation: "horizontal"
padding: [0,0,dp(24),0]
@@ -949,6 +1040,22 @@ MDScreen:
pos_hint: {"center_y": 0.3}
active: False
+ MDLabel:
+ text: "•"
+ font_style: "H6"
+ text_size: self.size
+ halign: "center"
+ size_hint_y: None
+ height: self.texture_size[1]
+ padding: [0, dp(2), 0, dp(22)]
+
+ MDLabel:
+ text: "Enabled Layers"
+ font_style: "H5"
+ size_hint_y: None
+ height: self.texture_size[1]
+ padding: [0, 0, 0, dp(12)]
+
MDBoxLayout:
orientation: "horizontal"
padding: [0,0,dp(24),0]
@@ -956,11 +1063,11 @@ MDScreen:
height: dp(48)
MDLabel:
- text: "Use online map sources"
+ text: "Local map source"
font_style: "H6"
MDSwitch:
- id: map_use_online
+ id: map_use_offline
pos_hint: {"center_y": 0.3}
active: False
@@ -971,11 +1078,86 @@ MDScreen:
height: dp(48)
MDLabel:
- text: "Use offline map source"
+ text: "OpenStreetMap"
font_style: "H6"
MDSwitch:
- id: map_use_offline
+ id: map_use_osm
+ pos_hint: {"center_y": 0.3}
+ active: False
+
+ MDBoxLayout:
+ orientation: "horizontal"
+ padding: [0,0,dp(24),0]
+ size_hint_y: None
+ height: dp(48)
+
+ MDLabel:
+ text: "MapTiler Topographic"
+ font_style: "H6"
+
+ MDSwitch:
+ id: map_use_topo
+ pos_hint: {"center_y": 0.3}
+ active: False
+
+ MDBoxLayout:
+ orientation: "horizontal"
+ padding: [0,0,dp(24),0]
+ size_hint_y: None
+ height: dp(48)
+
+ MDLabel:
+ text: "MapTiler Outdoor"
+ font_style: "H6"
+
+ MDSwitch:
+ id: map_use_outdoor
+ pos_hint: {"center_y": 0.3}
+ active: False
+
+ MDBoxLayout:
+ orientation: "horizontal"
+ padding: [0,0,dp(24),0]
+ size_hint_y: None
+ height: dp(48)
+
+ MDLabel:
+ text: "MapTiler Hybrid"
+ font_style: "H6"
+
+ MDSwitch:
+ id: map_use_hybrid
+ pos_hint: {"center_y": 0.3}
+ active: False
+
+ MDBoxLayout:
+ orientation: "horizontal"
+ padding: [0,0,dp(24),0]
+ size_hint_y: None
+ height: dp(48)
+
+ MDLabel:
+ text: "OpenTopo"
+ font_style: "H6"
+
+ MDSwitch:
+ id: map_use_opentopo
+ pos_hint: {"center_y": 0.3}
+ active: False
+
+ MDBoxLayout:
+ orientation: "horizontal"
+ padding: [0,0,dp(24),0]
+ size_hint_y: None
+ height: dp(48)
+
+ MDLabel:
+ text: "VirtualEarth"
+ font_style: "H6"
+
+ MDSwitch:
+ id: map_use_virtualearth
pos_hint: {"center_y": 0.3}
active: False
@@ -995,34 +1177,28 @@ MDScreen:
pos_hint: {"center_y": 0.3}
active: False
- MDBoxLayout:
- orientation: "vertical"
+ MDLabel:
+ text: "•"
+ font_style: "H6"
+ text_size: self.size
+ halign: "center"
size_hint_y: None
- spacing: dp(24)
- height: self.minimum_height
- padding: [0, dp(24), 0, 0]
+ height: self.texture_size[1]
+ padding: [0, dp(2), 0, dp(22)]
- MDRectangleFlatIconButton:
- id: map_select_button
- icon: "list-box-outline"
- text: "Select MBTiles Map"
- padding: [dp(0), dp(14), dp(0), dp(14)]
- icon_size: dp(24)
- font_size: dp(16)
- size_hint: [1.0, None]
- on_release: root.delegate.select_file_action(self)
- disabled: False
+ MDLabel:
+ text: "Map Provider API Keys"
+ font_style: "H5"
+ size_hint_y: None
+ height: self.texture_size[1]
+ padding: [0, 0, 0, dp(12)]
- MDRectangleFlatIconButton:
- id: map_cache_button
- icon: "map-clock-outline"
- text: "Clear map cache"
- padding: [dp(0), dp(14), dp(0), dp(14)]
- icon_size: dp(24)
- font_size: dp(16)
- size_hint: [1.0, None]
- on_release: root.delegate.clear_cache(self)
- disabled: False
+ MDTextField:
+ id: map_mt_api_key
+ hint_text: "Custom MapTiler API key"
+ text: ""
+ max_text_length: 128
+ font_size: dp(24)
"""
layout_map_screen = """
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────